using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace TD.TheGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    abstract public class Sprite : Microsoft.Xna.Framework.DrawableGameComponent
    {
        public SpriteEffects spriteEffects
        {
            get;
            set;
        }
        //determines if the sprite is at its end
        public bool endSprite;

        //All instances of Sprite need to have a texture
        public Texture2D texture
        {
            get;
            set;
        }

        //check if sprite is valid to update/draw
        public bool Active
        {
            get;
            set;
        }

        //Return the currentRectangle
        public Rectangle BoundingRectangle
        {
            get { return boundingRectangle; }
        }

        //The current state of the sprite
        public SpriteEnumState State
        {
            get;
            set;
        }

        //The old state of the sprite
        public SpriteEnumState oldState
        {
            get;
            set;
        }

        public int maxLoopTime
        {
            get;
            set;
        }

        protected Rectangle boundingRectangle;
        protected Rectangle[] current;
        protected int currentRect;
        public Color[] textureData;
        public Vector2 position;
        protected SpriteBatch _batch;
        protected TextureManager manager;
        private int currentLoopTime;
        protected AnimationHandler animationHandler;

        public Sprite(Game game, Vector2 position, bool active, int maxLoopTime, SpriteEnumState state)
            : base(game)
        {
            State = state;
            //Get services
            manager = (TextureManager)game.Services.GetService(typeof(TextureManager));
            _batch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            this.position = position;
            Active = active;
            texture = getTexture();
            spriteEffects = SpriteEffects.None;

            //Make array with all colors
            textureData = new Color[texture.Width * texture.Height];

            //sets rectangle to 1, if sprite has more rectangles, it will be declarated in that class
            current = new Rectangle[1];
            current[currentRect] = new Rectangle(0, 0, texture.Width, texture.Height);

            //set the loop time
            currentLoopTime = 0;
            this.maxLoopTime = maxLoopTime;

            //initialize animationhandler for spriteclasses
            animationHandler = new AnimationHandler();

            // TODO: Add your initialization code here
            boundingRectangle = new Rectangle((int)position.X, (int)position.Y, current[currentRect].Width, current[currentRect].Height);
        }

        protected abstract Texture2D getTexture();

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            base.LoadContent();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (Active)
            {
                endSprite = false;
                // TODO: Add your update code here
                currentLoopTime += gameTime.ElapsedGameTime.Milliseconds;
                if (currentLoopTime > maxLoopTime)
                {
                    currentLoopTime = 0;
                    currentRect++;
                    if (currentRect >= current.Count())
                    {
                        endSprite = true;
                        currentRect = 0;
                    }
                }
                updateBoundingRec();
                base.Update(gameTime);
            }
        }

        /// <summary>
        /// Allows the sprite to draw
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            if (Active)
            {
                //_batch.Begin();
                _batch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
                _batch.Draw(texture, position, current[currentRect], Color.White, 0f, new Vector2(0,0), new Vector2(1,1), spriteEffects, 0f);
                _batch.End();
            }
        }

        /// <summary>
        /// Update rectangle for each different frame
        /// </summary>
        private void updateBoundingRec()
        {
            boundingRectangle.X = (int)position.X;
            boundingRectangle.Y = (int)position.Y;
            boundingRectangle.Width = current[currentRect].Width;
            boundingRectangle.Height = current[currentRect].Height;
        }

        ///// <summary>
        ///// If pixels intersects, return true
        ///// </summary>
        ///// <param name="rectangle"></param>
        ///// <param name="data1"></param>
        ///// <param name="rect2"></param>
        ///// <param name="data2"></param>
        ///// <returns></returns>
        //public bool IntersectsPixel(Rectangle rect2, Color[] data2)
        //{
        //    int top = Math.Max(boundingRectangle.Top, rect2.Top);
        //    int bottom = Math.Min(boundingRectangle.Bottom, rect2.Bottom);
        //    int left = Math.Max(boundingRectangle.Left, rect2.Left);
        //    int right = Math.Min(boundingRectangle.Right, rect2.Right);

        //    for (int y = top; y < bottom; y++)
        //    {
        //        for (int x = left; x < right; x++)
        //        {
        //            Color colour1 = textureData[(x - boundingRectangle.Left) +
        //                (y - boundingRectangle.Top) * boundingRectangle.Width];
        //            Color colour2 = data2[(x - rect2.Left) +
        //                   (y - rect2.Top) * rect2.Width];

        //            if (colour1.A != 0 && colour2.A != 0)
        //            {
        //                return true;
        //            }
        //        }
        //    }
        //    return false;
        //}

        /// <summary>
        /// Check if rectangles are intersecting first, then check for pixelintersection
        /// </summary>
        /// <param name="rectangleB"></param>
        /// <param name="dataB"></param>
        /// <returns></returns>
        public bool Intersects(Rectangle rectangleB)
        {
            if (boundingRectangle.Intersects(rectangleB))
            {
                return true;
            }
            else
                return false;
        }

        /// <summary>
        ///  Resets the sprite!
        /// </summary>
        abstract public void Reset();

        public bool CollidesWith(Sprite other)
        {
            // Default behavior uses per-pixel collision detection
            return CollidesWith(other, true);
        }

        public bool CollidesWith(Sprite other, bool calcPerPixel)
        {
            // Get dimensions of texture
            int widthOther = other.texture.Width;
            int heightOther = other.texture.Height;
            int widthMe = texture.Width;
            int heightMe = texture.Height;

            if (BoundingRectangle.Intersects(other.BoundingRectangle))
            {
                if (calcPerPixel)
                {
                    if (PerPixelCollision(this, other))
                    {
                        return true;
                    }
                    return false;
                }
                else
                {
                    return true;
                }
            }

            return false;
        }

        // NOTE: Ain't this simple.
        private bool PerPixelCollision(Sprite a, Sprite b) // self, other
        {
            // Get Color data of each Texture
            Color[] bitsA = new Color[a.texture.Width * a.texture.Height];
            a.texture.GetData(bitsA);
            Color[] bitsB = new Color[b.texture.Width * b.texture.Height];
            b.texture.GetData(bitsB);

            // Calculate the intersecting rectangle
            int x1 = Math.Max(a.BoundingRectangle.X, b.BoundingRectangle.X); // Most right side of the two sprites
            int x2 = Math.Min(a.BoundingRectangle.X + a.BoundingRectangle.Width, b.BoundingRectangle.X + b.BoundingRectangle.Width);

            int y1 = Math.Max(a.BoundingRectangle.Y, b.BoundingRectangle.Y);
            int y2 = Math.Min(a.BoundingRectangle.Y + a.BoundingRectangle.Height, b.BoundingRectangle.Y + b.BoundingRectangle.Height);

            // For each single pixel in the intersecting rectangle
            for (int y = y1; y < y2; ++y)
            {
                for (int x = x1; x < x2; ++x)
                {
                    // Get the color from each texture
                    Color colora = bitsA[(x - a.BoundingRectangle.X) + (y - a.BoundingRectangle.Y) * a.texture.Width];
                    Color colorb = bitsB[(x - b.BoundingRectangle.X) + (y - b.BoundingRectangle.Y) * b.texture.Width];

                    if (colora.A != 0 && colorb.A != 0) // If both colors are not transparent (the alpha channel is not 0), then there is a collision
                    {
                        return true;
                    }
                }
            }
            // If no collision occurred by now, we're clear.
            return false;
        }
    }
}
